Scenario #9720: An Api Key Can Be Revoked by Deleting Its Api Key Subject

An API-key is revoked by permanently deleting its API_KEY subject: DELETE physically removes the subject together with its grants and its stored API-key hash, so the key immediately stops authenticating. There is no soft-delete for API_KEY subjects. As a safeguard against deleting the wrong subject, the request has to repeat the subject’s name and type as query parameters, which are verified against the subject identified by the UUID in the path.

Properties

Given

name value
subjectUuid a91c000b-0000-0000-0000-00000000000b
subjectName temporary.key

API_KEY subjects authenticate technical clients via the Hostsharing-Api-Key HTTP header instead of a Keycloak OIDC JWT, e.g. automation programs, completely bypassing Keycloak. Only a global-admin may create API_KEY subjects. The clear-text API-key is returned only once, in the response of creating the API_KEY subject; just its hash gets stored. Like GROUP subjects, API_KEY subjects cannot have an account. Global API_KEY subjects do not belong to a realm, thus their name must neither contain a - (the realm-prefix delimiter) nor a / (the GROUP subject marker).

Create the API_KEY Subject

The response contains the generated clear-text API-key (property apiKey) exactly once; it cannot be retrieved again.

HTTP POST "/api/rbac/subjects" \
  -H "Authorization: Bearer $HSADMINNG_JWT_BEARER" \
  `# {` \
  `#   "sub" : "uuid<hsh-alex_superuser>"` \
  `# }` \
  <<EOF
{
  "uuid" : "a91c000b-0000-0000-0000-00000000000b",
  "name" : "temporary.key",
  "type" : "API_KEY"
}
EOF
=> status: 201 CREATED a91c000b-0000-0000-0000-00000000000b
{
  "uuid" : "a91c000b-0000-0000-0000-00000000000b",
  "name" : "temporary.key",
  "organization" : "temporary",
  "type" : "API_KEY",
  "apiKey" : "hsak_temporary.key.b60265f64e9e6a43c9a0cc2f0b33a8a6f1455ea7e4ea04caec8dac77112791eb",
  "scopes" : null,
  "expiresAt" : null
}

Prerequisite: Resolve the UUID of the global ADMIN role

The grant API needs the UUID of the role which we want to grant.

HTTP GET "/api/rbac/roles?name=rbac.global%23global%3AADMIN" \
  -H "Authorization: Bearer $HSADMINNG_JWT_BEARER" \
  `# {` \
  `#   "sub" : "uuid<hsh-alex_superuser>"` \
  `# }`
=> status: 200 OK 
[ {
  "uuid" : "2366934b-3ba5-453a-a070-78698db295e0", // globalAdminRoleUuidToGrant
  "object.uuid" : "a8842cb7-7284-468a-9e78-07e6fea8bc98",
  "objectTable" : "rbac.global",
  "objectIdName" : "global",
  "roleType" : "ADMIN",
  "roleName" : "rbac.global#a8842cb7-7284-468a-9e78-07e6fea8bc98:ADMIN",
  "roleIdName" : "rbac.global#global:ADMIN"
} ]

Grant the global ADMIN role to the API_KEY Subject

HTTP POST "/api/rbac/grants" \
  -H "Authorization: Bearer $HSADMINNG_JWT_BEARER" \
  `# {` \
  `#   "sub" : "uuid<hsh-alex_superuser>"` \
  `# }` \
  -H 'Hostsharing-Assumed-Roles: rbac.global#global:ADMIN' \
  <<EOF
{
  "assumed" : true,
  "grantedRole.uuid" : "2366934b-3ba5-453a-a070-78698db295e0", // globalAdminRoleUuidToGrant
  "granteeSubject.uuid" : "a91c000b-0000-0000-0000-00000000000b"
}
EOF
=> status: 201 CREATED 2366934b-3ba5-453a-a070-78698db295e0 // globalAdminRoleUuidToGrant

Verify the API-key authenticates as its Subject with the global-admin role, without any JWT

HTTP GET "/api/hs/accounts/current" \
  -H "Hostsharing-Api-Key: $HSADMINNG_API_KEY"
=> status: 200 OK 
{
  "subject" : {
    "uuid" : "a91c000b-0000-0000-0000-00000000000b",
    "name" : "temporary.key",
    "organization" : "temporary",
    "type" : "API_KEY"
  },
  "person" : null,
  "globalAdmin" : true
}

Verify the API-key initially authenticates

HTTP GET "/api/rbac/context" \
  -H "Hostsharing-Api-Key: $HSADMINNG_API_KEY"
=> status: 200 OK 
{
  "subject" : {
    "uuid" : "a91c000b-0000-0000-0000-00000000000b",
    "name" : "temporary.key",
    "organization" : null,
    "type" : "API_KEY"
  },
  "assumedRoles" : [ ],
  "claimedGroups" : [ ],
  "effectiveGroups" : [ ],
  "globalAdmin" : true,
  "apiKey" : {
    "scopes" : [ ],
    "expiresAt" : null
  }
}

Revoke the API-key by deleting its API_KEY subject

HTTP DELETE "/api/rbac/subjects/a91c000b-0000-0000-0000-00000000000b?name=temporary.key&type=API_KEY" \
  -H "Authorization: Bearer $HSADMINNG_JWT_BEARER" \
  `# {` \
  `#   "sub" : "uuid<hsh-alex_superuser>"` \
  `# }`
=> status: 204 NO_CONTENT 

The revoked API-key no longer authenticates

HTTP GET "/api/rbac/context" \
  -H "Hostsharing-Api-Key: $HSADMINNG_API_KEY"
=> status: 401 UNAUTHORIZED 
{
  "path" : "/api/rbac/context",
  "statusCode" : 401,
  "statusPhrase" : "Unauthorized",
  "message" : "ERROR: [401] invalid API-key"
}

generated on 2026-08-10 04:34:38 for branch HEAD